/*
 *   Copyright 2012, Thomas Kerber
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */
package milk.skimmed;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;

import org.apache.bcel.classfile.ClassParser;

/**
 * Provides utility functions.
 * @author Thomas Kerber
 * @version 1.0.0
 */
public class Util{
    /**
     * The global logger for skimmed milk.
     */
    //TODO: log more. a lot more.
    //TODO: log jpatch here too.
    public static final Logger logger = Logger.getLogger("milk.skimmed");
    
    /**
     * Finds the directories which are root to class files.
     * 
     * Note: This works by introspecting the files for their class name.
     * @param root The root to scan.
     * @return The class file roots.
     */
    public static Set<File> findClassRoots(File root){
        // First, look for a class file. if one is found, ONLY this is
        // analysed.
        for(File f : root.listFiles()){
            if(f.getName().toLowerCase().endsWith(".class")){
                try{
                    String className = new ClassParser(f.getAbsolutePath()).
                            parse().getClassName();
                    int am = 0;
                    for(int i = 0; i < className.length(); i++)
                        if(className.charAt(i) == '.')
                            am++;
                    File curr = root;
                    for(int i = 0; i < am; i++)
                        curr = curr.getParentFile();
                    return Collections.singleton(curr);
                }
                // Exceptions are ignored. If a file can't be parsed, you
                // simply try with the next.
                catch(IOException e){}
            }
        }
        // Next, look for subdirs and scan them.
        Set<File> ret = new HashSet<File>();
        for(File f : root.listFiles()){
            if(f.isDirectory()){
                ret.addAll(findClassRoots(f));
            }
        }
        return ret;
    }
    
    /**
     * Finds all files with a certain name ending.
     * @param root The directory to scan.
     * @param ending The ending to scan for.
     * @return The files with the ending.
     */
    public static List<File> findAllWithEnding(File root, String ending){
        List<File> ret = new ArrayList<File>();
        for(File f : root.listFiles()){
            if(f.isFile() && f.getName().toLowerCase().endsWith(ending))
                ret.add(f);
            else if(f.isDirectory())
                ret.addAll(findAllWithEnding(f, ending));
        }
        return ret;
    }
    
    /**
     * Finds a directory with a certain name.
     * @param root The directory to scan.
     * @param dirName The name to look for.
     * @return The directory, or null if none was found.
     */
    public static File findDirWithName(File root, String dirName){
        for(File f : root.listFiles()){
            if(f.isFile())
                continue;
            else if(f.isDirectory()){
                if(f.getName().equals(dirName))
                    return f;
                File inSub = findDirWithName(f, dirName);
                if(inSub != null)
                    return inSub;
            }
        }
        return null;
    }
    
    /**
     * Copies a file object.
     * @param from From.
     * @param to To.
     * @throws IOException
     */
    public static void copy(File from, File to) throws IOException{
        if(from.isDirectory()){
            copyDir(from, to);
        }
        else{
            copyFile(from, to);
        }
    }
    
    /**
     * Copies a directory.
     * @param from From.
     * @param to To.
     * @throws IOException
     */
    public static void copyDir(File from, File to) throws IOException{
        for(String name : from.list()){
            File fromF = new File(from, name);
            File toF = new File(to, name);
            if(fromF.isDirectory()){
                toF.mkdir();
                copyDir(fromF, toF);
            }
            else if(fromF.isFile()){
                copyFile(fromF, toF);
            }
        }
    }
    
    /**
     * Copies a file.
     * @param from From.
     * @param to To.
     * @throws IOException
     */
    public static void copyFile(File from, File to) throws IOException{
        FileInputStream in = new FileInputStream(from);
        FileOutputStream out = new FileOutputStream(to);
        loop(in, out);
    }
    
    /**
     * Loops an input stream into an output stream.
     * @param in The input stream.
     * @param out The output stream.
     * @throws IOException
     */
    public static void loop(InputStream in, OutputStream out)
            throws IOException{
        milk.jpatch.Util.loop(in, out);
    }
    
    /**
     * Loops an input stream into an output stream.
     * @param in The input stream.
     * @param out The output stream.
     * @param close Whether or not to close the streams afterwards.
     * @throws IOException
     */
    public static void loop(InputStream in, OutputStream out, boolean close)
            throws IOException{
        milk.jpatch.Util.loop(in, out, close);
    }
    
    /**
     * Converts a byte array into a hex string.
     * @param bytes The bytes to convert.
     * @return The hex string.
     */
    public static String byteArrayToHex(byte[] bytes){
        StringBuilder b = new StringBuilder();
        for(byte i : bytes){
            for(int f = 0; f < 2; f++){
                b.append(Integer.toHexString(i % 16));
                i >>= 4;
            }
        }
        return b.toString();
    }
    
    /**
     * Creates a SHA-512 digest of a file.
     * @param f The file.
     * @return The digest.
     * @throws IOException
     */
    public static byte[] digest(File f) throws IOException{
        MessageDigest md;
        try{
            md = MessageDigest.getInstance("SHA-512");
        }
        catch(NoSuchAlgorithmException e){
            throw new Error(e);
        }
        FileInputStream in = new FileInputStream(f);
        byte[] buff = new byte[1 << 10];
        while(true){
            int am = in.read(buff);
            if(am <= 0)
                break;
            md.update(buff, 0, am);
        }
        in.close();
        return md.digest();
    }
    
}
